package se.vgregion.icc.riv.itintegration.monitoring;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;

import se.riv.itintegration.dependencies.v1.AbstractDependency;
import se.riv.itintegration.dependencies.v1.IntegrationComponentDependencies;
import se.riv.itintegration.monitoring.pingforconfiguration.v1.rivtabp21.PingForConfigurationResponderInterface;
import se.riv.itintegration.monitoring.pingforconfigurationresponder.v1.PingForConfigurationResponseType;
import se.riv.itintegration.monitoring.pingforconfigurationresponder.v1.PingForConfigurationType;
import se.vgregion.icc.PingHandlerRegistry;
import se.vgregion.icc.ResponseHelper;
import se.vgregion.icc.handlers.PingHandler;

/**
 * Service component for common usage of integration components implementation of the
 * riv:itintegration:monitoring:PingForConfiguration service.
 * 
 * @author ICC
 * @see {@link se.riv.itintegration.monitoring.pingforconfiguration.v1.rivtabp21.PingForConfigurationResponderInterface}
 */
public class PingForConfigurationComponent implements PingForConfigurationResponderInterface {
    private static final Logger LOG = LoggerFactory.getLogger(PingForConfigurationComponent.class);

    private static final String RIVTA_DATEFORMAT = "yyyyMMddHHmmss";
    private static final String NOT_DEFINED = "NOT DEFINED";
    private static final String ALREADY_PINGED = "ALREADY PINGED";
    private static final String XML_DEFINITION_REGEX = "\\<\\?xml(.+?)\\?\\>";

    /** The name of the property file containing the component version and other build time traceability info .*/
    private String propertyFilename;
    private String dependenciesFilename;

    @Autowired(required = true)
    private PingHandlerRegistry pingRegistry;

    @Required
    public void setPropertyFilename(String propertyFilename) {
        this.propertyFilename = propertyFilename;
    }

    public void setDependenciesFilename(String dependenciesFilename) {
        this.dependenciesFilename = dependenciesFilename;
    }

    /**
     * Ping service implementation. Retrieves information about the integration component.<br/>
     * 
     * If it have any dependencies it's going to send a ping request to them.
     * 
     * @param logicalAddress - see wsdl for riv:itintegration:monitoring:PingForConfiguration.
     * @param parameters - see wsdl for riv:itintegration:monitoring:PingForConfiguration.
     * @return see wsdl for riv:itintegration:monitoring:PingForConfiguration.
     */
    @Override
    public PingForConfigurationResponseType pingForConfiguration(String logicalAddress,
        PingForConfigurationType parameters) {

        PingForConfigurationResponseType response = new PingForConfigurationResponseType();
        SimpleDateFormat formatter = new SimpleDateFormat(RIVTA_DATEFORMAT);

        response.setPingDateTime(formatter.format(new Date()));

        Properties prop = getProperties();

        String version = prop.getProperty("component.version");

        if (version != null) {
            response.setVersion("V" + version);
        } else {
            response.setVersion(NOT_DEFINED);
        }

        ResponseHelper.addConfiguration(response, "URL", logicalAddress);

        ResponseHelper.addConfiguration(response, "SCM-Revision", prop.getProperty("buildNumber", NOT_DEFINED));
        ResponseHelper.addConfiguration(response, "SCM-Branch", prop.getProperty("scmBranch", NOT_DEFINED));
        String buildTime = prop.getProperty("timestamp");
        if (buildTime != null) {
            ResponseHelper
                .addConfiguration(response, "Build Time", formatter.format(new Date(Long.valueOf(buildTime))));
        }

        if (ResponseHelper.isAlreadyPinged(parameters, logicalAddress)) {
            ResponseHelper.addConfiguration(response, "Dependencies", ALREADY_PINGED);
            //Early return to avoid dependency loop.
            return response;
        }

        parameters.setLogicalAddress(parameters.getLogicalAddress() + "|" + logicalAddress);

        if (this.dependenciesFilename != null) {
            pingDependenciesAndAddToResponse(parameters, response);
        }

        return response;
    }

    /**
     * Get a Properties table from the appointed property file.
     * 
     * @return Properties the properties table
     */
    protected Properties getProperties() {
        ResourceBundle rb = ResourceBundle.getBundle(propertyFilename);
        Properties prop = convertResourceBundleToProperties(rb);
        return prop;
    }

    /***
     * Unmarshal dependencies from the dependency file and tries to ping them.
     * 
     * @param parameters the PingForConfigurationType that holds the components thats already pinged
     * @param response the response that you going to add dependency response to
     */
    private void pingDependenciesAndAddToResponse(PingForConfigurationType parameters,
        PingForConfigurationResponseType response) {
        IntegrationComponentDependencies dependencies;

        try {
            dependencies = getUnmarshalledDependenciesFromFile(this.dependenciesFilename);
            if (dependencies != null) {
                addDependenciesInResponseConfigurationTag(response, parameters, dependencies);
            }
        } catch (JAXBException e) {
            e =
                new JAXBException(
                    "Could not load dependencies file, please make sure it has valid format if you wish to use the "
                        + "extended functionality.");
        }
    }

    /**
     * Convert ResourceBundle into a Properties object.
     * 
     * @param resource
     *            a resource bundle to convert.
     * @return Properties a properties version of the resource bundle.
     */
    private Properties convertResourceBundleToProperties(ResourceBundle resource) {
        Properties properties = new Properties();

        Enumeration<String> keys = resource.getKeys();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement();
            properties.put(key, resource.getString(key));
        }

        return properties;
    }

    /***
     * Takes the <code>IntegrationComponentDependencies</code>, performs a ping on them and adds 
     * the dependency response to the parent response.
     * 
     * @param parameters the PingForConfigurationType that holds the components thats already pinged
     * @param dependencies the dependencies that your going to ping
     */
    private void addDependenciesInResponseConfigurationTag(PingForConfigurationResponseType response,
        PingForConfigurationType parameters, IntegrationComponentDependencies dependencies) {

        String responseFromDependency = "";
        List<AbstractDependency> dependencyList = dependencies.getDependencies();

        if (isListNotEmptyOrNull(dependencyList)) {
            PingHandler pingHandler = null;
            for (AbstractDependency dependency : dependencyList) {

                pingHandler = pingRegistry.getPingHandler(dependency);
                responseFromDependency = pingHandler.preformPingAndReturnResponse(dependency, parameters);
                try {
                    ResponseHelper.addAlreadyPingedToResponse(responseFromDependency, parameters);
                } catch (Exception e) {
                    e = new Exception("Could not add the already pinged dependencies");
                }

                responseFromDependency = responseFromDependency.replaceAll(XML_DEFINITION_REGEX, "").trim();
                ResponseHelper.addConfiguration(response, "Dependency", responseFromDependency);
            }
        }
    }

    /***
     * Gets the dependency list from the dependencies.xml file.
     * 
     * @param dependenciesFilePath filepath to where the dependency file exists.
     * @return IntegrationComponenetDependencies the specific projects dependencies
     * @throws JAXBException - if you can't unmarshal the file
     */
    public IntegrationComponentDependencies getUnmarshalledDependenciesFromFile(String dependenciesFilePath)
        throws JAXBException {
        File file = new File(dependenciesFilePath);

        LOG.debug("Checking if filepath is ok");

        if (file.isFile()) {

            LOG.debug("Tries to unmarshall the file");
            JAXBContext jaxbContext = JAXBContext.newInstance(IntegrationComponentDependencies.class);

            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
            JAXBElement<IntegrationComponentDependencies> integrationComponentDependencies =
                jaxbUnmarshaller.unmarshal(new StreamSource(file), IntegrationComponentDependencies.class);

            return integrationComponentDependencies.getValue();
        }

        return null;
    }

    /***
     * Checks if the list isn't empty or null.
     * 
     * @param list The list to check
     * @return true if list isn't empty or null, else false.
     */
    private boolean isListNotEmptyOrNull(List<?> list) {
        if (list != null && !list.isEmpty()) {
            return true;
        }
        return false;
    }
}
